Примеры программ

Третий уровень сложности

1. Магический квадрат на numpy (Декларативный стиль).
Проверка матрицы на магический квадрат с использованием срезов numpy.
(Лекция 2)

import numpy as np

def is_magic(M_list):
    M = np.array(M_list) # Превращаем в массив numpy
    n = len(M)

    # Собираем все суммы в одно множество:
    # 1. Сумма главной диагонали: np.trace(M) или sum(M.diagonal())
    # 2. Сумма побочной: sum(np.fliplr(M).diagonal())
    # 3. Суммы строк: np.sum(M, axis=1)
    # 4. Суммы столбцов: np.sum(M, axis=0)

    # В лекции использовался подход со срезами и генераторами:
    s_diag1 = sum(M[i, i] for i in range(n))
    s_diag2 = sum(M[i, n-1-i] for i in range(n))
    s_rows = {sum(M[i, :]) for i in range(n)} # Срез строки i
    s_cols = {sum(M[:, j]) for j in range(n)} # Срез столбца j

    all_sums = {s_diag1, s_diag2} | s_rows | s_cols
    return len(all_sums) == 1

2. Каноническое разложение на множители (Решето Эратосфена + Словарь словарей).
Получить список словарей, где M[i] = {простой_множитель: степень}.
(Лекция 3)

from collections import defaultdict
from copy import copy

def get_factors(n):
    # 1. Сначала находим один простой делитель для каждого числа (как в решете)
    # L[i] хранит минимальный простой делитель числа i
    min_prime = [0] * (n + 1)
    for i in range(2, n + 1):
        if min_prime[i] == 0:
            for j in range(i, n + 1, i):
                if min_prime[j] == 0: min_prime[j] = i

    # 2. Динамически строим словари разложений
    # Разложение числа i = Разложение (i // p) + множитель p
    factors = [defaultdict(int) for _ in range(n + 1)]

    for i in range(2, n + 1):
        p = min_prime[i]
        # Берем разложение меньшего числа (копируем!)
        prev_factors = factors[i // p]
        factors[i] = copy(prev_factors)
        # Добавляем текущий делитель
        factors[i][p] += 1

    return factors

3. Быстрое возведение в степень с кешем (Мемоизация).
Рекурсия + словарь для запоминания промежуточных степеней.
(Лекция 4)

cache = {}
def power_cached(a, n):
    key = (a, n) # Ключ - кортеж аргументов
    if key not in cache:
        if n == 0: return 1
        if n % 2 == 0:
            # a^n = (a^(n/2))^2
            half = power_cached(a, n // 2)
            cache[key] = half * half
        else:
            cache[key] = a * power_cached(a, n - 1)
    return cache[key]

4. Палиндром максимальной длины (Рекурсия + Кеш + Срезы).
Смотри пример 2 из второго уровня, но здесь акцент на использование @lru_cache или ручного словаря для оптимизации.
(Лекция 5)

memo = {}
def max_palindrome(s):
    if s in memo: return memo[s]
    if len(s) <= 1: return s
    if s[0] == s[-1]:
        res = s[0] + max_palindrome(s[1:-1]) + s[-1]
    else:
        p1 = max_palindrome(s[:-1])
        p2 = max_palindrome(s[1:])
        res = p1 if len(p1) > len(p2) else p2
    memo[s] = res
    return res

5. Декоратор кеша как класс.
Реализация декоратора не через функцию, а через класс с методами __call__ и __getitem__.
(Лекция 12)

class Cache:
    def __init__(self, func):
        self.func = func
        self.memory = {}

    def __call__(self, n):
        if n not in self.memory:
            self.memory[n] = self.func(n)
        return self.memory[n]

    def __getitem__(self, n): # Позволяет писать fib[10]
        return self.__call__(n)

@Cache
def fib(n):
    if n <= 2: return 1
    return fib(n-1) + fib(n-2)

# Вызов: print(fib(10)) или print(fib[10])

6. Композиция функций через перегрузку умножения.
Реализовать синтаксис (f * g)(x) означающий f(g(x)).
(Лекция 12)

class Function:
    def __init__(self, func):
        self.func = func

    def __call__(self, x):
        return self.func(x)

    def __mul__(self, other):
        # Возвращаем новую "Функцию", которая применяет self к результату other
        def composition(x):
            return self.func(other(x))
        return Function(composition)

@Function
def square(x): return x * x

@Function
def inc(x): return x + 1

# (x+1)^2 для x=2 -> 3^2 = 9
h = square * inc
print(h(2)) 

7. Карринг (Частичное применение).
Превращение функции f(a, b) в f(a)(b).
(Лекция 12)

def curry(func):
    def curried(a):
        def inner(b):
            return func(a, b)
        return inner
    return curried

@curry
def add(a, b):
    return a + b

add_5 = add(5) # Функция, прибавляющая 5
print(add_5(10)) # 15

8. Ленивая формула (Expression Tree).
Реализация отложенного вычисления c = a + b, где a и b можно менять.
(Лекция 13)

class Expr:
    def __init__(self, left, op, right):
        self.left, self.op, self.right = left, op, right

    def __call__(self): # Вычисление происходит ЗДЕСЬ
        l_val = self.left() if callable(self.left) else self.left
        r_val = self.right() if callable(self.right) else self.right
        if self.op == '+': return l_val + r_val

class Var:
    def __init__(self, val): self.val = val
    def __call__(self, new_val=None):
        if new_val is not None: self.val = new_val
        return self.val
    def __add__(self, other):
        return Expr(self, '+', other)

x = Var(10)
y = Var(20)
f = x + y   # Выражение создано, но не вычислено
print(f())  # 30
x(50)       # Меняем x
print(f())  # 70 (формула пересчиталась)

9. Метакласс – класс со списком.
Метакласс, который автоматически добавляет все созданные классы-наследники или их экземпляры в реестр.
(Лекция 13)

class RegistryMeta(type):
    def __init__(cls, name, bases, attrs):
        if not hasattr(cls, 'instances'):
            cls.instances = [] # Создаем список в каждом новом классе
        super().__init__(name, bases, attrs)

class Base(metaclass=RegistryMeta):
    def __init__(self):
        self.__class__.instances.append(self)

class A(Base): pass
class B(Base): pass

a = A()
b = B()
print(A.instances) # [a]
print(B.instances) # [b]

10. Асинхронные рекурсивные генераторы.
Генерация чисел Фибоначчи асинхронно с async for.
(Лекция 14)

import asyncio

async def async_fib(n):
    a, b = 0, 1
    for _ in range(n):
        await asyncio.sleep(0.1) # Имитация асинхронной задержки
        yield a
        a, b = b, a + b

async def main():
    async for val in async_fib(5):
        print(val)

# Запуск: asyncio.run(main())

11. Поиск максимального квадрата из нулей с кешированием.
Реализация задачи из Лекции 5, но в полном варианте, который требуется на "отлично".

# Ключ кеша: (y, x, L) - координаты левого верхнего угла и размер
cache = {}

def max_zeros_square(M, y=0, x=0, L=None):
    if L is None: L = len(M)
    state = (y, x, L)

    if state not in cache:
        # 1. Проверяем текущий квадрат (с помощью генератора и all)
        is_full_zeros = all(M[r][c] == 0 for r in range(y, y+L) for c in range(x, x+L))

        if is_full_zeros:
            cache[state] = L
        else:
            # 2. Если нет, рекурсивно ищем в 4-х вложенных, уменьшая размер L-1
            # Важно: граничные условия рекурсии должны быть обработаны (L=0 -> 0)
            if L <= 1: 
                cache[state] = 0 # Квадрат 1x1 не из нуля -> размер 0
            else:
                res = max(
                    max_zeros_square(M, x, y, L-1),
                    max_zeros_square(M, x+1, y, L-1),
                    max_zeros_square(M, x, y+1, L-1),
                    max_zeros_square(M, x+1, y+1, L-1)
                )
                cache[state] = res

    return cache[state]
← Меню